


/*
 * A lot of this code (especially everything) was taken directly
 * or nearly directly from spellchk.c, .
 * Hacked together by Matt McCorry, March 2009 to ~~~
 * http://developer.pidgin.im/wiki/CHowTo/BasicPluginHowto
 */

#ifndef PURPLE_PLUGINS //header guard
#define PURPLE_PLUGINS
#endif /* PURPLE_PLUGINS */


#include <glib.h>

#include "debug.h"
#include "notify.h"
#include "plugin.h"
#include "version.h"

#include "internal.h"
#include "pidgin.h"

#include "gtkplugin.h"
#include "gtkprefs.h"
#include "gtkutils.h"

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>


#define PLUGIN_ID "core-asiansmilies"
#define ASIANSMILIES_OBJECT_KEY "asiansmilies"

#define PROP_SMILIES     "asiansmilies::smilies" //i really don't understand this.  some sort of identifier for the menus

//these enums are for the columns in the model var
enum {
	TEXT_COLUMN,
	N_COLUMNS
};

struct _asiansmilies {
	GtkTextView *view;
	GtkTextMark *mark_insert_start;
	GtkTextMark *mark_insert_end;
};

typedef struct _asiansmilies asiansmilies;

/* we're adding this here and assigning it in plugin_load because we need
 * a valid plugin handle for our call to purple_notify_message() in the
 * plugin_action_test_cb() callback function */
PurplePlugin *asiansmilies_plugin = NULL;

static GtkListStore *model;

static GtkWidget *tree;
static GtkWidget *new_smilie;

static void save_list(void);
static void attach_menu_to_conversations(void);

static void on_edited(GtkCellRendererText *cellrenderertext,
					  gchar *path, gchar *arg2, gpointer data)
{
	GtkTreeIter iter;
	GValue val;

	if (arg2[0] == '\0') {
		gdk_beep();
		return;
	}

	g_return_if_fail(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(model), &iter, path));
	val.g_type = 0;
	gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, TEXT_COLUMN, &val);

	if (strcmp(arg2, g_value_get_string(&val))) {
		gtk_list_store_set(model, &iter, TEXT_COLUMN, arg2, -1);
		save_list();
	}
	g_value_unset(&val);
}


static void list_add_new(void)
{
	
	GtkTreeIter iter;
	const char *word = gtk_entry_get_text(GTK_ENTRY(new_smilie));


	if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) {
		char *tmpword = g_utf8_casefold(word, -1);
		do {
			//todo: lol hack
			GValue bad_val;
			gboolean match;
			bad_val.g_type = 0;

			gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, TEXT_COLUMN, &bad_val);

			purple_debug_info(PLUGIN_ID, "add new. line n124");
			char *bad = NULL;
			bad = g_utf8_casefold(g_value_get_string(&bad_val), -1);
			match = !strcmp(bad, tmpword);
			g_free(bad);
			purple_debug_info(PLUGIN_ID, "About to test out the match in the add sequence");
			if (match) {
	
				g_free(tmpword);

				purple_notify_error(NULL, _("Duplicate Smilie"), _("The specified smilie already exists in the list."), gtk_entry_get_text(GTK_ENTRY(new_smilie)));
				return;
			}

			g_value_unset(&bad_val);

		} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter));

		g_free(tmpword);
	}


	gtk_list_store_append(model, &iter);
	gtk_list_store_set(model, &iter,
		TEXT_COLUMN, word,
		-1);


	gtk_editable_delete_text(GTK_EDITABLE(new_smilie), 0, -1);

	gtk_widget_grab_focus(new_smilie);

	save_list();
}

static void add_selected_row_to_list(GtkTreeModel *model, GtkTreePath *path,
	GtkTreeIter *iter, gpointer data)
{
	GtkTreeRowReference *row_reference;
	GSList **list = (GSList **)data;
	row_reference = gtk_tree_row_reference_new(model, path);
	*list = g_slist_prepend(*list, row_reference);
}

static void remove_row(void *data1, gpointer data2)
{
	GtkTreeRowReference *row_reference;
	GtkTreePath *path;
	GtkTreeIter iter;

	row_reference = (GtkTreeRowReference *)data1;
	path = gtk_tree_row_reference_get_path(row_reference);

	if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
		gtk_list_store_remove(model, &iter);

	gtk_tree_path_free(path);
	gtk_tree_row_reference_free(row_reference);
}

static void list_delete(void)
{
	GtkTreeSelection *sel;
	GSList *list = NULL;

	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
	gtk_tree_selection_selected_foreach(sel, add_selected_row_to_list, &list);

	g_slist_foreach(list, remove_row, NULL);
	g_slist_free(list);

	save_list();
}

//saves the smilies into the config file.  I've mildly altered it.
static void save_list()
{
	GString *data;
	GtkTreeIter iter;

	data = g_string_new("");

	if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) {
		do {
			GValue val0;

			val0.g_type = 0;

			gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, TEXT_COLUMN, &val0);


			g_string_append_printf(data, "%s\n", g_value_get_string(&val0));

			g_value_unset(&val0);

		} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter));
	}

	purple_util_write_data_to_file("smilies", data->str, -1);
	attach_menu_to_conversations();

	g_string_free(data, TRUE);
}

//this is used for parsing the input form the buffer.  I've not changed a thing in in.
static int buf_get_line(char *ibuf, char **buf, int *position, gsize len)
{
	int pos = *position;
	int spos = pos;

	if (pos == len)
		return 0;

	while (!(ibuf[pos] == '\n' ||
	         (ibuf[pos] == '\r' && ibuf[pos + 1] != '\n')))
	{
		pos++;
		if (pos == len)
			return 0;
	}

	if (pos != 0 && ibuf[pos] == '\n' && ibuf[pos - 1] == '\r')
		ibuf[pos - 1] = '\0';

	ibuf[pos] = '\0';
	*buf = &ibuf[spos];

	pos++;
	*position = pos;

	return 1;
}

static void load_conf(void)
{
	/* Load up the defualt smilies
	 */
	const char * const defaultconf =
			"ㅇㅅㅇ\n"
			"ㅠ_ㅠ\n"
			"＼(＾▽＾*)いらっしゃ～いっ(*＾▽＾)／\n";
	gchar *buf; //holds the location of the smilies file
	gchar *ibuf; // holds the contents of the smilies file

	char smilietxt[256] = "";
	int pnt = 0;
	gsize size;
	purple_debug_info(PLUGIN_ID, "About to make that annoying folder");
	buf = g_build_filename(purple_user_dir(), "smilies", NULL);
	purple_debug_info(PLUGIN_ID, "Made that annoying folder");
	g_file_get_contents(buf, &ibuf, &size, NULL);//attempt to load smilies
	g_free(buf);//clear buffer of the file location
	if (!ibuf) {//if empty
		ibuf = g_strdup(defaultconf);
		size = strlen(defaultconf);
	}

	model = gtk_list_store_new((gint)N_COLUMNS, G_TYPE_STRING);

	//purple_debug_info(PLUGIN_ID, "About to try and parse the list of smilies. size = %d, ibuf =%s, defaultconf = ", size, ibuf, defaultconf);
	while (buf_get_line(ibuf, &buf, &pnt, size)) {
		GtkTreeIter iter;
		strncpy(smilietxt, buf, 256);

		gtk_list_store_append(model, &iter);
		gtk_list_store_set(model, &iter, TEXT_COLUMN, smilietxt, -1);
		//purple_debug_info(PLUGIN_ID, "Attempting to add smilie. smilie = %s", smilietxt);	
	
	}
	g_free(ibuf);
	//todo: this isn't the right place to save, but i'll fix it later
	save_list();
}

static void remove_asiansmilies_menu(gpointer data, gpointer dontcare)
{
	PidginWindow *win = data;
	GtkWidget *menu;

	menu = g_object_get_data(G_OBJECT(win->window), PROP_SMILIES);
	if (menu)
	{
		gtk_widget_destroy(menu);
		g_object_set_data(G_OBJECT(win->window), PROP_SMILIES, NULL);
		
		/* XXX: Do we need to set PROP_SMILIES data to NULL for each gtkconv->imhtml as well?
		 * It doesn't seem to be necessary right now. The GTF library probably gets loaded
		 * at the very beginning when Purple starts, and not when this plugin is loaded. */
	}
}

static gboolean
plugin_unload(PurplePlugin *plugin)
{
	g_list_foreach(pidgin_conv_windows_get_list(), remove_asiansmilies_menu, NULL);

	return TRUE;
}

static void
smilie_clicked_cb(GtkWidget *w, PidginWindow *win)
{

		PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win);
		GtkTextBuffer *buffer = gtkconv->entry_buffer;
		const gchar * word = gtk_menu_item_get_label(w);
		GtkTextIter end;
		gtk_text_buffer_get_iter_at_offset(buffer, &end, 0);
		gtk_text_iter_forward_to_end(&end);

		purple_debug_info("Smilies:", "about to attempt to insert. word=%s, buffer=%s\n", word, buffer);
		gtk_text_buffer_insert(buffer, &end, word, -1);
	
}

static void
regenerate_asiansmilies_menu(PidginConversation *gtkconv)
{
	PidginWindow *win;
	GtkWidget *menu;
	int count;
	GtkWidget *mitem, *item;
	GSList *list = NULL;
	GtkTreeIter iter;

	if (gtkconv == NULL)
		return;

	win = pidgin_conv_get_window(gtkconv);
	if (win == NULL)
		return;

	mitem = g_object_get_data(G_OBJECT(win->window), PROP_SMILIES);
	if (mitem != NULL)
	{
		gtk_widget_destroy(mitem);
		//g_object_set_data(G_OBJECT(win->window), PROP_SMILIES, NULL);
	}
	
	mitem = gtk_menu_item_new_with_mnemonic(_("_AsianSmilies"));
	gtk_menu_shell_insert(GTK_MENU_SHELL(win->menu.menubar), mitem, 3);
	g_object_set_data(G_OBJECT(win->window), PROP_SMILIES, mitem);
	gtk_widget_show(mitem);


	menu = gtk_menu_new();
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), menu);

	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(smilie_clicked_cb), win);

	if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) {
		do {
			GValue val0;

			val0.g_type = 0;

			gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, TEXT_COLUMN, &val0);

			item = gtk_menu_item_new_with_label(g_value_get_string(&val0));
/*			g_object_set_data(G_OBJECT(item), PROP_SMILIES, (gpointer)filter);*/
/*			list = gtk_menu_item_get_group(GTK_MENU_ITEM(item));*/

			g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(smilie_clicked_cb), win);

			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);

			g_value_unset(&val0);

		} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter));
	}

	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(smilie_clicked_cb), win);

	gtk_widget_show_all(menu);
}

static void attach_asiansmilies_menu(gpointer data, gpointer dontcare)
{
	PidginWindow *win = data;
	PidginConversation *gtkconv;

	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
	regenerate_asiansmilies_menu(gtkconv);
}

static void
conversation_switched_cb(PurpleConversation *conv)
{
	//if you detach a tab from a window you loose the smilies menu.  this will recreate it
	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
	regenerate_asiansmilies_menu(gtkconv);
}

static void
conversation_created_cb(PurpleConversation *conv)
{
	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);

	/* hopefully fix a crash related to persistent convs */
	if(gtkconv == NULL)
		return;

/*	Add the menu in here*/
	attach_asiansmilies_menu(pidgin_conv_get_window(gtkconv), NULL);
}

static void attach_menu_to_conversations(void)
{
	g_list_foreach(pidgin_conv_windows_get_list(), attach_asiansmilies_menu, NULL);
}

static gboolean
plugin_load(PurplePlugin *plugin) {
	void *conv_handle = purple_conversations_get_handle();
	GList *convs;
	//load list of items
	load_conf();

	asiansmilies_plugin = plugin; /* assign this here so we have a valid handle later */

	/* Attach to existing conversations */

	/* Add a `AsianSmilies' menu in the conversation window */
	purple_signal_connect(conv_handle, "conversation-created", plugin,
				PURPLE_CALLBACK(conversation_created_cb), NULL);
	purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
				plugin, PURPLE_CALLBACK(conversation_switched_cb), NULL);


	attach_menu_to_conversations();
	
    	return TRUE;
}

static gboolean non_empty(const char *s)
{
	while (*s && g_ascii_isspace(*s))
		s++;
	return *s;
}

static void on_entry_changed(GtkEditable *editable, gpointer data)
{
	gtk_widget_set_sensitive((GtkWidget*)data,
		non_empty(gtk_entry_get_text(GTK_ENTRY(new_smilie))));
}

static void on_selection_changed(GtkTreeSelection *sel,
	gpointer data)
{
	gint num_selected;
#if GTK_CHECK_VERSION(2,2,0)
	num_selected = gtk_tree_selection_count_selected_rows(sel);
#else
	gtk_tree_selection_selected_foreach(sel, count_selected_helper, &num_selected);
#endif
	gtk_widget_set_sensitive((GtkWidget*)data, (num_selected > 0));
}

static GtkWidget *
get_config_frame(PurplePlugin *plugin)
{
	purple_debug_info(PLUGIN_ID, "Entering into get_config_name");

	GtkWidget *ret, *vbox, *win;
	GtkWidget *hbox;
	GtkWidget *button;
	GtkSizeGroup *sg;
	GtkSizeGroup *sg2;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;
	GtkWidget *vbox2;
	GtkWidget *vbox3;

	ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
	gtk_container_set_border_width (GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);

	vbox = pidgin_make_frame(ret, _("Smilies"));
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
	gtk_widget_show(vbox);
	
	
	win = gtk_scrolled_window_new(0, 0);
	gtk_box_pack_start(GTK_BOX(vbox), win, TRUE, TRUE, 0);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(win),
										GTK_SHADOW_IN);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(win),
			GTK_POLICY_NEVER,
			GTK_POLICY_ALWAYS);
	gtk_widget_show(win);

	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
	gtk_widget_set_size_request(tree, -1, 200);



	renderer = gtk_cell_renderer_text_new();
	g_object_set(G_OBJECT(renderer),
		"editable", TRUE,
		NULL);
	g_signal_connect(G_OBJECT(renderer), "edited",
		G_CALLBACK(on_edited), GINT_TO_POINTER(1));
	column = gtk_tree_view_column_new_with_attributes(_("Smilies"), renderer, "text", TEXT_COLUMN, NULL);

	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width(column, 400);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	
	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)),
		 GTK_SELECTION_MULTIPLE);
	gtk_container_add(GTK_CONTAINER(win), tree);
	gtk_widget_show(tree);

	hbox = gtk_hbutton_box_new();
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
	g_signal_connect(G_OBJECT(button), "clicked",
			   G_CALLBACK(list_delete), NULL);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_set_sensitive(button, FALSE);

	g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree))),
		"changed", G_CALLBACK(on_selection_changed), button);

	gtk_widget_show(button);

	vbox = pidgin_make_frame(ret, _("Add a new smilie"));

	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
	gtk_widget_show(hbox);
	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
	gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, TRUE, 0);
	gtk_widget_show(vbox2);

	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
	sg2 = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);

	new_smilie = gtk_entry_new();
	gtk_size_group_add_widget(sg2, new_smilie);
	pidgin_add_widget_to_vbox(GTK_BOX(vbox2), _("New _smilie:"), sg, new_smilie, FALSE, NULL);



	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
	g_signal_connect(G_OBJECT(button), "clicked",
			   G_CALLBACK(list_add_new), NULL);
	vbox3 = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), vbox3, TRUE, FALSE, 0);
	gtk_widget_show(vbox3);
	gtk_box_pack_end(GTK_BOX(vbox3), button, FALSE, FALSE, 0);
	g_signal_connect(G_OBJECT(new_smilie), "changed", G_CALLBACK(on_entry_changed), button);
	gtk_widget_set_sensitive(button, FALSE);
	gtk_widget_show(button);

#if 0
	vbox = pidgin_make_frame(ret, _("General Text Replacement Options"));
	pidgin_prefs_checkbox(_("Enable replacement of last word on send"),
	                        "/plugins/gtk/spellchk/last_word_replace", vbox);
#endif

	gtk_widget_show_all(ret);
	g_object_unref(sg);
	g_object_unref(sg2);
	return ret;
}

static PidginPluginUiInfo ui_info = {
	get_config_frame,
	0,
	NULL,

	/* padding */
	NULL,
	NULL,
	NULL,
	NULL
};

static PurplePluginInfo info = {
    PURPLE_PLUGIN_MAGIC,
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    PURPLE_PLUGIN_STANDARD,
    PIDGIN_PLUGIN_TYPE,
    0,
    NULL,
    PURPLE_PRIORITY_DEFAULT,

    "core-sulgorae-asiansmilies",
    "Asian Smilies",
    "0.1",

    "Shortcuts to asian style ASCII smilies",          
    "Asian Smilies Plugin",          
    "Matt McCorry <mccorry@gmail.com>",                          
    "http://goatse.cx",     
    
    plugin_load,                   
    plugin_unload,                          
    NULL,                          
                                   
    &ui_info,                          
    NULL,                          
    NULL,                        
    NULL,    
            
    NULL,                          
    NULL,                          
    NULL,                          
    NULL                           
};                               
    
static void                        
init_plugin(PurplePlugin *plugin)
{
                                  
}

PURPLE_INIT_PLUGIN(asian_smilies, init_plugin, info)
